summaryrefslogtreecommitdiff
path: root/apps/web/src/app/editor/[project_id]
diff options
context:
space:
mode:
authorMaze <167211895+mazeincoding@users.noreply.github.com>2026-01-31 00:20:04 +0100
committerGitHub <noreply@github.com>2026-01-31 00:20:04 +0100
commit7bf0984698e43b9aa2dd879f65951318ed410390 (patch)
treeccf2cd590a47d550152afc07129cb55c71a9ec35 /apps/web/src/app/editor/[project_id]
parent0173db99448d1c4c6415dcf15272e17551b48b04 (diff)
downloadopencut-7bf0984698e43b9aa2dd879f65951318ed410390.tar.gz
codebase overhaul (#697)
Diffstat (limited to 'apps/web/src/app/editor/[project_id]')
-rw-r--r--apps/web/src/app/editor/[project_id]/layout.tsx9
-rw-r--r--apps/web/src/app/editor/[project_id]/page.tsx527
2 files changed, 88 insertions, 448 deletions
diff --git a/apps/web/src/app/editor/[project_id]/layout.tsx b/apps/web/src/app/editor/[project_id]/layout.tsx
deleted file mode 100644
index c37e78b7..00000000
--- a/apps/web/src/app/editor/[project_id]/layout.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-"use client";
-
-export default function EditorLayout({
- children,
-}: {
- children: React.ReactNode;
-}) {
- return <div>{children}</div>;
-}
diff --git a/apps/web/src/app/editor/[project_id]/page.tsx b/apps/web/src/app/editor/[project_id]/page.tsx
index 1b53b6b5..e652e928 100644
--- a/apps/web/src/app/editor/[project_id]/page.tsx
+++ b/apps/web/src/app/editor/[project_id]/page.tsx
@@ -1,459 +1,108 @@
"use client";
-import { useEffect, useRef } from "react";
-import { useParams, useRouter } from "next/navigation";
+import { useParams } from "next/navigation";
import {
- ResizablePanelGroup,
- ResizablePanel,
- ResizableHandle,
+ ResizablePanelGroup,
+ ResizablePanel,
+ ResizableHandle,
} from "@/components/ui/resizable";
-import { MediaPanel } from "@/components/editor/media-panel";
-import { PropertiesPanel } from "@/components/editor/properties-panel";
+import { AssetsPanel } from "@/components/editor/panels/assets";
+import { PropertiesPanel } from "@/components/editor/panels/properties";
import { Timeline } from "@/components/editor/timeline";
-import { PreviewPanel } from "@/components/editor/preview-panel";
+import { PreviewPanel } from "@/components/editor/panels/preview";
import { EditorHeader } from "@/components/editor/editor-header";
-import { usePanelStore } from "@/stores/panel-store";
-import { useProjectStore } from "@/stores/project-store";
import { EditorProvider } from "@/components/providers/editor-provider";
-import { usePlaybackControls } from "@/hooks/use-playback-controls";
import { Onboarding } from "@/components/editor/onboarding";
+import { MigrationDialog } from "@/components/editor/dialogs/migration-dialog";
+import { usePanelStore } from "@/stores/panel-store";
export default function Editor() {
- const {
- toolsPanel,
- previewPanel,
- mainContent,
- timeline,
- setToolsPanel,
- setPreviewPanel,
- setMainContent,
- setTimeline,
- propertiesPanel,
- setPropertiesPanel,
- activePreset,
- resetCounter,
- } = usePanelStore();
-
- const {
- activeProject,
- loadProject,
- createNewProject,
- isInvalidProjectId,
- markProjectIdAsInvalid,
- } = useProjectStore();
- const params = useParams();
- const router = useRouter();
- const projectId = params.project_id as string;
- const handledProjectIds = useRef<Set<string>>(new Set());
- const isInitializingRef = useRef<boolean>(false);
-
- usePlaybackControls();
-
- useEffect(() => {
- let isCancelled = false;
-
- const initProject = async () => {
- if (!projectId) {
- return;
- }
-
- // Prevent duplicate initialization
- if (isInitializingRef.current) {
- return;
- }
-
- // Check if project is already loaded
- if (activeProject?.id === projectId) {
- return;
- }
-
- // Check global invalid tracking first (most important for preventing duplicates)
- if (isInvalidProjectId(projectId)) {
- return;
- }
-
- // Check if we've already handled this project ID locally
- if (handledProjectIds.current.has(projectId)) {
- return;
- }
-
- // Mark as initializing to prevent race conditions
- isInitializingRef.current = true;
- handledProjectIds.current.add(projectId);
-
- try {
- await loadProject(projectId);
-
- // Check if component was unmounted during async operation
- if (isCancelled) {
- return;
- }
-
- // Project loaded successfully
- isInitializingRef.current = false;
- } catch (error) {
- // Check if component was unmounted during async operation
- if (isCancelled) {
- return;
- }
-
- // More specific error handling - only create new project for actual "not found" errors
- const isProjectNotFound =
- error instanceof Error &&
- (error.message.includes("not found") ||
- error.message.includes("does not exist") ||
- error.message.includes("Project not found"));
-
- if (isProjectNotFound) {
- // Mark this project ID as invalid globally BEFORE creating project
- markProjectIdAsInvalid(projectId);
-
- try {
- const newProjectId = await createNewProject("Untitled Project");
-
- // Check again if component was unmounted
- if (isCancelled) {
- return;
- }
-
- router.replace(`/editor/${newProjectId}`);
- } catch (createError) {
- console.error("Failed to create new project:", createError);
- }
- } else {
- // For other errors (storage issues, corruption, etc.), don't create new project
- console.error(
- "Project loading failed with recoverable error:",
- error
- );
- // Remove from handled set so user can retry
- handledProjectIds.current.delete(projectId);
- }
-
- isInitializingRef.current = false;
- }
- };
+ const params = useParams();
+ const projectId = params.project_id as string;
- initProject();
-
- // Cleanup function to cancel async operations
- return () => {
- isCancelled = true;
- isInitializingRef.current = false;
- };
- }, [
- projectId,
- loadProject,
- createNewProject,
- router,
- isInvalidProjectId,
- markProjectIdAsInvalid,
- ]);
-
- return (
- <EditorProvider>
- <div className="h-screen w-screen flex flex-col bg-background overflow-hidden">
- <EditorHeader />
- <div className="flex-1 min-h-0 min-w-0">
- {activePreset === "media" ? (
- <ResizablePanelGroup
- key={`media-${activePreset}-${resetCounter}`}
- direction="horizontal"
- className="h-full w-full gap-[0.18rem] px-3 pb-3"
- >
- <ResizablePanel
- defaultSize={toolsPanel}
- minSize={15}
- maxSize={40}
- onResize={setToolsPanel}
- className="min-w-0 rounded-sm"
- >
- <MediaPanel />
- </ResizablePanel>
-
- <ResizableHandle withHandle />
-
- <ResizablePanel
- defaultSize={100 - toolsPanel}
- minSize={60}
- className="min-w-0 min-h-0"
- >
- <ResizablePanelGroup
- direction="vertical"
- className="h-full w-full gap-[0.18rem]"
- >
- <ResizablePanel
- defaultSize={mainContent}
- minSize={30}
- maxSize={85}
- onResize={setMainContent}
- className="min-h-0"
- >
- <ResizablePanelGroup
- direction="horizontal"
- className="h-full w-full gap-[0.19rem]"
- >
- <ResizablePanel
- defaultSize={previewPanel}
- minSize={30}
- onResize={setPreviewPanel}
- className="min-w-0 min-h-0 flex-1"
- >
- <PreviewPanel />
- </ResizablePanel>
-
- <ResizableHandle withHandle />
-
- <ResizablePanel
- defaultSize={propertiesPanel}
- minSize={15}
- maxSize={40}
- onResize={setPropertiesPanel}
- className="min-w-0"
- >
- <PropertiesPanel />
- </ResizablePanel>
- </ResizablePanelGroup>
- </ResizablePanel>
-
- <ResizableHandle withHandle />
-
- <ResizablePanel
- defaultSize={timeline}
- minSize={15}
- maxSize={70}
- onResize={setTimeline}
- className="min-h-0"
- >
- <Timeline />
- </ResizablePanel>
- </ResizablePanelGroup>
- </ResizablePanel>
- </ResizablePanelGroup>
- ) : activePreset === "inspector" ? (
- <ResizablePanelGroup
- key={`inspector-${activePreset}-${resetCounter}`}
- direction="horizontal"
- className="h-full w-full gap-[0.18rem] px-3 pb-3"
- >
- <ResizablePanel
- defaultSize={100 - propertiesPanel}
- minSize={30}
- onResize={(size) => setPropertiesPanel(100 - size)}
- className="min-w-0 min-h-0"
- >
- <ResizablePanelGroup
- direction="vertical"
- className="h-full w-full gap-[0.18rem]"
- >
- <ResizablePanel
- defaultSize={mainContent}
- minSize={30}
- maxSize={85}
- onResize={setMainContent}
- className="min-h-0"
- >
- <ResizablePanelGroup
- direction="horizontal"
- className="h-full w-full gap-[0.19rem]"
- >
- <ResizablePanel
- defaultSize={toolsPanel}
- minSize={15}
- maxSize={40}
- onResize={setToolsPanel}
- className="min-w-0 rounded-sm"
- >
- <MediaPanel />
- </ResizablePanel>
-
- <ResizableHandle withHandle />
-
- <ResizablePanel
- defaultSize={previewPanel}
- minSize={30}
- onResize={setPreviewPanel}
- className="min-w-0 min-h-0 flex-1"
- >
- <PreviewPanel />
- </ResizablePanel>
- </ResizablePanelGroup>
- </ResizablePanel>
-
- <ResizableHandle withHandle />
-
- <ResizablePanel
- defaultSize={timeline}
- minSize={15}
- maxSize={70}
- onResize={setTimeline}
- className="min-h-0"
- >
- <Timeline />
- </ResizablePanel>
- </ResizablePanelGroup>
- </ResizablePanel>
-
- <ResizableHandle withHandle />
-
- <ResizablePanel
- defaultSize={propertiesPanel}
- minSize={15}
- maxSize={40}
- onResize={setPropertiesPanel}
- className="min-w-0 min-h-0"
- >
- <PropertiesPanel />
- </ResizablePanel>
- </ResizablePanelGroup>
- ) : activePreset === "vertical-preview" ? (
- <ResizablePanelGroup
- key={`vertical-preview-${activePreset}-${resetCounter}`}
- direction="horizontal"
- className="h-full w-full gap-[0.18rem] px-3 pb-3"
- >
- <ResizablePanel
- defaultSize={100 - previewPanel}
- minSize={30}
- onResize={(size) => setPreviewPanel(100 - size)}
- className="min-w-0 min-h-0"
- >
- <ResizablePanelGroup
- direction="vertical"
- className="h-full w-full gap-[0.18rem]"
- >
- <ResizablePanel
- defaultSize={mainContent}
- minSize={30}
- maxSize={85}
- onResize={setMainContent}
- className="min-h-0"
- >
- <ResizablePanelGroup
- direction="horizontal"
- className="h-full w-full gap-[0.19rem]"
- >
- <ResizablePanel
- defaultSize={toolsPanel}
- minSize={15}
- maxSize={40}
- onResize={setToolsPanel}
- className="min-w-0 rounded-sm"
- >
- <MediaPanel />
- </ResizablePanel>
-
- <ResizableHandle withHandle />
-
- <ResizablePanel
- defaultSize={propertiesPanel}
- minSize={15}
- maxSize={40}
- onResize={setPropertiesPanel}
- className="min-w-0"
- >
- <PropertiesPanel />
- </ResizablePanel>
- </ResizablePanelGroup>
- </ResizablePanel>
-
- <ResizableHandle withHandle />
-
- <ResizablePanel
- defaultSize={timeline}
- minSize={15}
- maxSize={70}
- onResize={setTimeline}
- className="min-h-0"
- >
- <Timeline />
- </ResizablePanel>
- </ResizablePanelGroup>
- </ResizablePanel>
+ return (
+ <EditorProvider projectId={projectId}>
+ <div className="bg-background flex h-screen w-screen flex-col overflow-hidden">
+ <EditorHeader />
+ <div className="min-h-0 min-w-0 flex-1">
+ <EditorLayout />
+ </div>
+ <Onboarding />
+ <MigrationDialog />
+ </div>
+ </EditorProvider>
+ );
+}
- <ResizableHandle withHandle />
+function EditorLayout() {
+ const { panels, setPanel } = usePanelStore();
- <ResizablePanel
- defaultSize={previewPanel}
- minSize={30}
- onResize={setPreviewPanel}
- className="min-w-0 min-h-0"
- >
- <PreviewPanel />
- </ResizablePanel>
- </ResizablePanelGroup>
- ) : (
- <ResizablePanelGroup
- key={`default-${activePreset}-${resetCounter}`}
- direction="vertical"
- className="h-full w-full gap-[0.18rem]"
- >
- <ResizablePanel
- defaultSize={mainContent}
- minSize={30}
- maxSize={85}
- onResize={setMainContent}
- className="min-h-0"
- >
- {/* Main content area */}
- <ResizablePanelGroup
- direction="horizontal"
- className="h-full w-full gap-[0.19rem] px-3"
- >
- {/* Tools Panel */}
- <ResizablePanel
- defaultSize={toolsPanel}
- minSize={15}
- maxSize={40}
- onResize={setToolsPanel}
- className="min-w-0 rounded-sm"
- >
- <MediaPanel />
- </ResizablePanel>
+ return (
+ <ResizablePanelGroup
+ direction="vertical"
+ className="size-full gap-[0.18rem]"
+ onLayout={(sizes) => {
+ setPanel("mainContent", sizes[0] ?? panels.mainContent);
+ setPanel("timeline", sizes[1] ?? panels.timeline);
+ }}
+ >
+ <ResizablePanel
+ defaultSize={panels.mainContent}
+ minSize={30}
+ maxSize={85}
+ className="min-h-0"
+ >
+ <ResizablePanelGroup
+ direction="horizontal"
+ className="size-full gap-[0.19rem] px-3"
+ onLayout={(sizes) => {
+ setPanel("tools", sizes[0] ?? panels.tools);
+ setPanel("preview", sizes[1] ?? panels.preview);
+ setPanel("properties", sizes[2] ?? panels.properties);
+ }}
+ >
+ <ResizablePanel
+ defaultSize={panels.tools}
+ minSize={15}
+ maxSize={40}
+ className="min-w-0 rounded-sm"
+ >
+ <AssetsPanel />
+ </ResizablePanel>
- <ResizableHandle withHandle />
+ <ResizableHandle withHandle />
- {/* Preview Area */}
- <ResizablePanel
- defaultSize={previewPanel}
- minSize={30}
- onResize={setPreviewPanel}
- className="min-w-0 min-h-0 flex-1"
- >
- <PreviewPanel />
- </ResizablePanel>
+ <ResizablePanel
+ defaultSize={panels.preview}
+ minSize={30}
+ className="min-h-0 min-w-0 flex-1"
+ >
+ <PreviewPanel />
+ </ResizablePanel>
- <ResizableHandle withHandle />
+ <ResizableHandle withHandle />
- <ResizablePanel
- defaultSize={propertiesPanel}
- minSize={15}
- maxSize={40}
- onResize={setPropertiesPanel}
- className="min-w-0 rounded-sm"
- >
- <PropertiesPanel />
- </ResizablePanel>
- </ResizablePanelGroup>
- </ResizablePanel>
+ <ResizablePanel
+ defaultSize={panels.properties}
+ minSize={15}
+ maxSize={40}
+ className="min-w-0 rounded-sm"
+ >
+ <PropertiesPanel />
+ </ResizablePanel>
+ </ResizablePanelGroup>
+ </ResizablePanel>
- <ResizableHandle withHandle />
+ <ResizableHandle withHandle />
- {/* Timeline */}
- <ResizablePanel
- defaultSize={timeline}
- minSize={15}
- maxSize={70}
- onResize={setTimeline}
- className="min-h-0 px-3 pb-3"
- >
- <Timeline />
- </ResizablePanel>
- </ResizablePanelGroup>
- )}
- </div>
- <Onboarding />
- </div>
- </EditorProvider>
- );
+ <ResizablePanel
+ defaultSize={panels.timeline}
+ minSize={15}
+ maxSize={70}
+ className="min-h-0 px-3 pb-3"
+ >
+ <Timeline />
+ </ResizablePanel>
+ </ResizablePanelGroup>
+ );
}